package com.leonzhangxf.spel.reference;

import com.leonzhangxf.spel.reference.bean.Inventor;
import com.leonzhangxf.spel.reference.bean.Society;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;

/**
 * Operators
 *
 * @author leonzhangxf
 * @date 20190427
 */
public abstract class Operators {

    /**
     * To demonstrate the operators expression.
     */
    public abstract void demonstrate();

    public static void main(String[] args) {
        System.out.println("============================RelationalOperators");
        Operators operators = new RelationalOperators();
        operators.demonstrate();
        System.out.println("============================LogicalOperators");
        Operators operators1 = new LogicalOperators();
        operators1.demonstrate();
        System.out.println("============================MathematicalOperators");
        Operators operators2 = new MathematicalOperators();
        operators2.demonstrate();
        System.out.println("============================AssignmentOperator");
        AssignmentOperator operators3 = new AssignmentOperator();
        operators3.demonstrate();
    }

    /**
     * The relational operators (equal, not equal, less than, less than or equal,
     * greater than, and greater than or equal) are supported by using standard operator notation.
     * The following listing shows a few examples of operators。
     *
     * <p>
     * Greater-than and less-than comparisons against null follow a simple rule:
     * null is treated as nothing (that is NOT as zero).
     * As a consequence, any other value is always greater than null (X > null is always true)
     * and no other value is ever less than nothing (X < null is always false).
     * If you prefer numeric comparisons instead, avoid number-based null comparisons in favor of comparisons
     * against zero (for example, X > 0 or X < 0).
     *
     * <p>
     * Be careful with primitive types, as they are immediately boxed up to the wrapper type,
     * so 1 instanceof T(int) evaluates to false while 1 instanceof T(Integer) evaluates to true, as expected.
     *
     * <p>
     * Each symbolic operator can also be specified as a purely alphabetic equivalent.
     * This avoids problems where the symbols used have special meaning for the document type in which
     * the expression is embedded (such as in an XML document). The textual equivalents are:
     * <ol>
     * <li>lt (<)</li>
     * <li>gt (>)</li>
     * <li>le (<=)</li>
     * <li>ge (>=)</li>
     * <li>eq (==)</li>
     * <li>ne (!=)</li>
     * <li>div (/)</li>
     * <li>mod (%)</li>
     * <li>not (!)</li>
     * </ol>
     * All of the textual operators are case-insensitive.
     */
    public static class RelationalOperators extends Operators {

        @Override
        public void demonstrate() {
            SpelParserConfiguration parserConfiguration =
                new SpelParserConfiguration(true, true);
            SpelExpressionParser parser = new SpelExpressionParser(parserConfiguration);

            // evaluates to true
            Boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
            System.out.println(trueValue);

            // evaluates to false
            Boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
            System.out.println(falseValue);

            // evaluates to true
            Boolean value = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
            System.out.println(value);

            System.out.println("-----------------------");
            // In addition to the standard relational operators,
            // SpEL supports the instanceof and regular expression-based matches operator.
            // The following listing shows examples of both:
            // evaluates to false
            System.out.println(parser.parseExpression("'xyz' instanceof T(Integer)")
                .getValue(Boolean.class));
            // evaluates to true
            System.out.println(parser.parseExpression("'xyz' instanceof T(String)")
                .getValue(Boolean.class));

            // evaluates to true
            System.out.println(parser.parseExpression(
                "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class));

            //evaluates to false
            System.out.println(parser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'")
                .getValue(Boolean.class));
        }
    }

    /**
     * SpEL supports the following logical operators:
     * <ol>
     * <li>and</li>
     * <li>or</li>
     * <li>not</li>
     * </ol>
     */
    public static class LogicalOperators extends Operators {

        @Override
        public void demonstrate() {
            SpelParserConfiguration parserConfiguration =
                new SpelParserConfiguration(true, true);
            SpelExpressionParser parser = new SpelExpressionParser(parserConfiguration);

            StandardEvaluationContext societyContext = new StandardEvaluationContext();
            Society society = new Society();
            society.getMembers().add(new Inventor("Mihajlo Pupin", "China"));
            society.getMembers().add(new Inventor("Nikola Tesla", "China"));
            society.getMembers().add(new Inventor("Albert Einstein", "China"));

            societyContext.setRootObject(society);

            // -- AND --
            // evaluates to false
            System.out.println(parser.parseExpression("true and false").getValue(Boolean.class));
            // evaluates to true
            String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
            System.out.println(parser.parseExpression(expression).getValue(societyContext, Boolean.class));

            // -- OR --
            // evaluates to true
            System.out.println(parser.parseExpression("true or false").getValue(Boolean.class));
            // evaluates to true
            String expression1 = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
            System.out.println(parser.parseExpression(expression1).getValue(societyContext, Boolean.class));

            // -- NOT --
            // evaluates to false
            System.out.println(parser.parseExpression("!true").getValue(Boolean.class));

            // -- AND and NOT --
            // evaluates to false
            String expression2 = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
            System.out.println(parser.parseExpression(expression2).getValue(societyContext, Boolean.class));
        }
    }

    /**
     * You can use the addition operator on both numbers and strings.
     * You can use the subtraction, multiplication, and division operators only on numbers.
     * You can also use the modulus (%) and exponential power (^) operators.
     * Standard operator precedence is enforced.
     */
    public static class MathematicalOperators extends Operators {

        @Override
        public void demonstrate() {
            SpelParserConfiguration parserConfiguration =
                new SpelParserConfiguration(true, true);
            SpelExpressionParser parser = new SpelExpressionParser(parserConfiguration);

            // Addition
            // 2
            System.out.println(parser.parseExpression("1 + 1").getValue(Integer.class));
            // 'test string'
            System.out.println(parser.parseExpression("'test' + ' ' + 'string'").getValue(String.class));

            // Subtraction
            // 4
            System.out.println(parser.parseExpression("1 - -3").getValue(Integer.class));
            // -9000
            System.out.println(parser.parseExpression("1000.00 - 1e4").getValue(Double.class));

            // Multiplication
            // 6
            System.out.println(parser.parseExpression("-2 * -3").getValue(Integer.class));
            // 24.0
            System.out.println(parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class));

            // Division
            // -2
            System.out.println(parser.parseExpression("6 / -3").getValue(Integer.class));
            // 1.0
            System.out.println(parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class));

            // Modulus
            // 3
            System.out.println(parser.parseExpression("7 % 4").getValue(Integer.class));
            // 1
            System.out.println(parser.parseExpression("8 / 5 % 2").getValue(Integer.class));

            // Operator precedence
            // -21
            System.out.println(parser.parseExpression("1+2-3*8").getValue(Integer.class));
        }
    }

    /**
     * To setting a property, use the assignment operator (=).
     * This is typically done within a call to setValue but can also be done inside a call to getValue.
     */
    public static class AssignmentOperator extends Operators {

        @Override
        public void demonstrate() {
            SpelParserConfiguration parserConfiguration =
                new SpelParserConfiguration(true, true);
            SpelExpressionParser parser = new SpelExpressionParser(parserConfiguration);

            Inventor inventor = new Inventor();
            EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

            parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic");

            System.out.println(parser.parseExpression("Name").getValue(context, inventor, String.class));

            // alternatively
            System.out.println(parser.parseExpression("Name = 'Aleksandar Seovic666'")
                .getValue(context, inventor, String.class));
        }
    }
}
